import {v4 as uuid} from "uuid";
/**
 Created by:
 muhtearjan mehmood مۇختەرجان مەخمۇت (Email: yeganaaa@163.com)
 At 2020-01-12 12:02 چۈشتىن بۇرۇن
 */
import {EntityBase} from "entity_base";
/**
 * Data change notification
 */
export interface IStream<T> {
    /**
     * Called when all data should be reset, e.g. the all data in server side is deleted and client side should clean all data.
     */
    reset(): Promise<void>
    /**
     * Called when client side application loaded, the client side fetches data from the server,
     * @param data
     */
    onDataReceived(data: T[]): Promise<void>
    /**
     * Called when the data has been deleted from the server, client should be remove these data from itself.
     * @param data
     */
    onDeleted(data: T[]): Promise<void>
    /**
     * Called when data is newly created in server side
     * @param data
     */
    onCreated(data: T[]): Promise<void>
    /**
     * Called when data changed in server side.
     * @param data
     */
    onChanged(data: T[]): Promise<void>
}

export interface IStore<T> {
    commit(action: string, ...payload: any): void
}

/**
 Created by:
 muhtearjan mehmood مۇختەرجان مەخمۇت (Email: yeganaaa@163.com)
 At 2020-01-14 10:04 چۈشتىن بۇرۇن
 */

/** Examples
import Vue from 'vue'
import Vuex from 'vuex'
import {Description, Name, Tag} from "@/Shared/MusicControlCenter/Entity";
import {AutomaticStreamStore} from "@/store/AutomaticStreamStore";

Vue.use(Vuex)

interface IStore {
  tags: Tag[]
}
const mutationObject = {}
export const store = new Vuex.Store<IStore>({
  state: {
    tags: []
  },
  mutations: mutationObject,
  actions: {
  },
  modules: {

  }
})
export const IStreamModules = {
  tag: new AutomaticStreamStore<Tag, IStore>(
      "Tags",
      store => store.state.tags,
      (store, data) => store.state.tags = data,
      () => new Tag(new Name(), new Description())
  ).build(store, mutationObject)
}
 */

export interface StreamHooks<ENTITY extends EntityBase<ENTITY>> {
    onChanged(data: ENTITY[]): Promise<void>
    onCreated(data: ENTITY[]): Promise<void>
    onDataReceived(data: ENTITY[]): Promise<void>
    onDeleted(data: ENTITY[]): Promise<void>
    reset(): Promise<void>
}
export function generateEmptyHook<ENTITY extends EntityBase<ENTITY>>(): StreamHooks<ENTITY> {
    return {
        async onChanged(data: ENTITY[]): Promise<void> {

        }, async onCreated(data: ENTITY[]): Promise<void> {

        }, async onDataReceived(data: ENTITY[]): Promise<void> {

        }, async onDeleted(data: ENTITY[]): Promise<void> {

        }, async reset(): Promise<void> {

        }
    }
}

export class IStreamCallbackImplementation<ENTITY extends EntityBase<ENTITY>> implements IStream<ENTITY> {
    constructor(private callback: StreamHooks<ENTITY>, public beforeExecutionHook: StreamHooks<ENTITY> = generateEmptyHook(), public afterExecutionHook: StreamHooks<ENTITY> = generateEmptyHook(),) {}
    async onChanged(data: ENTITY[]): Promise<void> {
        await this.beforeExecutionHook.onChanged(data)
        await this.callback.onChanged(data)
        await this.afterExecutionHook.onChanged(data)
    }

    async onCreated(data: ENTITY[]): Promise<void> {
        await this.beforeExecutionHook.onCreated(data)
        await this.callback.onCreated(data)
        await this.afterExecutionHook.onCreated(data)
    }

    async onDataReceived(data: ENTITY[]): Promise<void> {
        await this.beforeExecutionHook.onDataReceived(data)
        await this.callback.onDataReceived(data)
        await this.afterExecutionHook.onDataReceived(data)
    }

    async onDeleted(data: ENTITY[]): Promise<void> {
        await this.beforeExecutionHook.onDeleted(data)
        await this.callback.onDeleted(data)
        await this.afterExecutionHook.onDeleted(data)
    }

    async reset(): Promise<void> {
        await this.beforeExecutionHook.reset()
        await this.callback.reset()
        await this.afterExecutionHook.reset()
    }
}

export class AutomaticStreamStore<ENTITY extends EntityBase<ENTITY>, STORE_STATE> {
    constructor(private moduleName: string, private obtainStoreObject: () => IStore<STORE_STATE>, public getEntitiesFromStore: (storeState: STORE_STATE) => ENTITY[], private setEntitiesToStore: (storeState: STORE_STATE, data: ENTITY[]) => void, public entityConstructor: () => ENTITY) {
    }

    build(mutationObject: any): IStream<ENTITY> {
        const updateWhenExistsAndReturnIdWhenNotExists = (storeState: STORE_STATE, payload: ENTITY[]) => {
            const notExists: ENTITY[] = []
            const data = this.getEntitiesFromStore(storeState)
            const hashMap = new Map<string, ENTITY>()
            data.forEach(item => hashMap.set(item._id, item))
            payload.forEach(item => {
                const obj = hashMap.get(item._id)
                if (obj == null) {
                    notExists.push(item)
                    return
                }
                obj.copyFrom(item)
            })
            return notExists
        }
        const names = {
            onCreated: `${this.moduleName}Created`,
            onChanged: `${this.moduleName}Changed`,
            onDataReceived: `${this.moduleName}DataReceived`,
            onDeleted: `${this.moduleName}Deleted`,
            onReset: `${this.moduleName}Reset`,
        }
        mutationObject[names.onCreated] = (storeState: STORE_STATE, payload: ENTITY[]) => payload.forEach(item => {
            const data = this.getEntitiesFromStore(storeState)
            const hashMap = new Map<string, ENTITY>()
            data.forEach(item => hashMap.set(item._id, item))
            payload.forEach(item => {
                const obj = hashMap.get(item._id)
                if (obj == null) return
                obj.copyFrom(item)
            })
            this.getEntitiesFromStore(storeState).push(this.entityConstructor().copyFrom(item).copyFrom(item))
        })
        mutationObject[names.onDataReceived] = (storeState: STORE_STATE, payload: ENTITY[]) => {
            const all: ENTITY[] = []
            updateWhenExistsAndReturnIdWhenNotExists(storeState, payload).forEach(item => all.push(this.entityConstructor().copyFrom(item).copyFrom(item)))
            this.getEntitiesFromStore(storeState).push(...all)
        }
        mutationObject[names.onChanged] = (storeState: STORE_STATE, payload: ENTITY[]) => {
            updateWhenExistsAndReturnIdWhenNotExists(storeState, payload)
        }
        mutationObject[names.onDeleted] = (storeState: STORE_STATE, payload: ENTITY[]) => {
            let objs: ENTITY[] = []
            objs.push(...this.getEntitiesFromStore(storeState))
            payload.forEach(entity => {
                objs = objs.filter(item => item._id != entity._id)
            })
            this.setEntitiesToStore(storeState, objs)
        }
        mutationObject[names.onReset] = (state: STORE_STATE) => this.setEntitiesToStore(state, [])
        const self = this
        return new IStreamCallbackImplementation({
            async onChanged(data: ENTITY[]): Promise<void> {
                self.obtainStoreObject().commit(names.onChanged, data)
            }, async onCreated(data: ENTITY[]): Promise<void> {
                self.obtainStoreObject().commit(names.onCreated, data)
            }, async onDataReceived(data: ENTITY[]): Promise<void> {
                self.obtainStoreObject().commit(names.onDataReceived, data)
            }, async onDeleted(data: ENTITY[]): Promise<void> {
                self.obtainStoreObject().commit(names.onDeleted, data)
            }, async reset(): Promise<void> {
                self.obtainStoreObject().commit(names.onReset)
            }
        })
    }
}
